'''
Contains the functions needed for at least two of the Dell TechDirect APIs:
Dispatch, Warranty, and Web Case Generation.
'''
from os.path import (
    abspath as os_path_abspath,
    basename as os_path_basename,
    isfile as os_path_isfile
)
from json import dumps as json_dumps, loads as json_loads
from json.decoder import JSONDecodeError
from datetime import datetime
from xmltodict import parse as xmltodict_parse
from requests import post as requests_post
from dateparser import parse as dateparser_parse
import PySimpleGUI as sg
from warranty_manager.claim.common import (
    SUBMIT_COLOR, VIEW_COLOR, CANCEL_COLOR, TEXT_LABEL_SIZE, TEXT_INPUT_SIZE
)
from warranty_manager.gui.confirmation import get_confirmation


def get_error_message(res_xml):
    '''
    Gets a message for any errors when using the Warranty or Dispatch APIs,
    which are usually provided in XML format.
    '''
    soapenv_envelope = res_xml.get('soapenv:Envelope')

    # Missing claim information (e.g., token, username, password)
    if soapenv_envelope:
        fault_code = res_xml['soapenv:Envelope']['soapenv:Body']['soapenv:Fault']['faultcode']
        fault_string = res_xml['soapenv:Envelope']['soapenv:Body']['soapenv:Fault']['faultstring']
        msg = f'Fault Code {fault_code}: {fault_string}.'
    else:
        msg = res_xml['soap:Envelope']['soap:Body']['soap:Fault']['faultstring']

    return msg


def get_api_keys(api_name):
    '''Gets the specified Dell API's Client ID and Client Secret.'''
    client_id = None
    secret = None

    # To make sure the text label is always long enough, regardless of API
    secret_label = f'Dell {api_name} Client Secret'
    secret_length = len(secret_label)

    layout = [
        [
            sg.T( f'Dell {api_name} Client ID', size=(secret_length, 1) ),
            sg.I(key='-ID-')
        ],
        [
            sg.T( secret_label, size=(secret_length, 1) ),
            sg.I(key='-SECRET-', password_char='*')
        ],
        [
            sg.Submit(button_color=SUBMIT_COLOR),
            sg.Cancel(button_color=CANCEL_COLOR)
        ]
    ]

    window = sg.Window('Dell API Keys', layout, keep_on_top=True)

    while True:
        event, values = window.read()

        if event in (None, 'Cancel'):
            break
        elif event == 'Submit':
            client_id = values['-ID-'].strip()
            secret = values['-SECRET-'].strip()

            if not client_id or not secret:
                sg.popup_error(
                    'You must enter a Client ID and Secret!', title='Error', keep_on_top=True
                )
                continue

            break
        else:
            continue

    window.close()
    return (client_id, secret)


def request_access_token(api_name, now_string, sandbox=False):
    '''
    Requests a new API access token, for the Dell Warranty, Dispatch,
    and Web Case Generation APIs.
    '''
    client_id, secret = get_api_keys(api_name)

    if client_id and secret:
        if sandbox is True:
            url = "https://apigtwb2cnp.us.dell.com/auth/oauth/v2/token"
        else:
            url = "https://apigtwb2c.us.dell.com/auth/oauth/v2/token"

        res = requests_post(
            url=url,
            data={
                'grant_type': 'client_credentials',
                'client_id': client_id,
                'client_secret': secret,
                'Content-Type': 'application/x-www-form-urlencoded',
            }
        )

        try:
            token = res.json().get('access_token')
        except (JSONDecodeError, AttributeError):
            sg.popup_error(
                get_error_message( xmltodict_parse(res.text) ),
                title='Error', keep_on_top=True
            )
            return ('', '') # To avoid an IndexError, when accessing the token
        else:
            with open(f'{api_name} token.json', 'w') as json_writer:
                json_writer.write(
                    json_dumps({ api_name: {'token': token, 'date': now_string} })
                )

            return ( token, os_path_abspath(f'{api_name} token.json') )


def get_access_token(api_name, sandbox=False):
    '''
    Gets the Dell access token from the specified JSON file. If the token is
    still valid, it uses that one; otherwise, it requests a new token.
    You can also manually request a new token, using the provided button.
    '''
    token = None

    # To make sure the API name is always titlecase
    api_name = api_name.title().strip()

    # To clarify whether a sandbox or production token is being requested
    if sandbox is True:
        sandbox_text = ' (sandbox)'
    else:
        sandbox_text = ''

    layout = [
        [sg.T(f'{api_name} API Token'), sg.I(key='-TOKEN-'), sg.FileBrowse()],
        [
            sg.Button(
                'Get Token from File',
                button_color=SUBMIT_COLOR,
                tooltip='Gets the API token from a JSON file'
            ),
            sg.Button(
                'Get New Token',
                button_color=('black', '#ffd700'),
                tooltip='Requests a new API access token from Dell'
            ),
            sg.Cancel(button_color=CANCEL_COLOR)
        ]
    ]

    window = sg.Window(f'Dell API Access Token{sandbox_text}', layout)

    while True:
        event, values = window.read()

        if event in (None, 'Cancel'):
            break
        elif event == 'Get Token from File':

            # To make sure value in token field is a JSON file
            if not os_path_isfile(values['-TOKEN-']) \
            or not os_path_basename(values['-TOKEN-']).lower().endswith('.json'):
                sg.popup_error('You must select a JSON file!', title='Error')
                window['-TOKEN-'].update('')
                continue

            with open(values['-TOKEN-'], 'r') as json_reader:
                try:
                    token_dict = json_loads( json_reader.read() )
                    token_date = dateparser_parse( token_dict.get(api_name).get('date') )
                except (JSONDecodeError, AttributeError):
                    sg.popup_error(
                        "The Selected file doesn't contain a valid token!",
                        title='Error', keep_on_top=True
                    )
                    window['-TOKEN-'].update('')
                    continue

                # To get the current token, or a new one, if it's expired
                now = datetime.now()

                if (now - token_date).total_seconds() > 3600:
                    sg.popup(
                        'The provided token has expired, so a new one is being requested...',
                        title='Info',
                        keep_on_top=True
                    )

                    token = request_access_token(
                        api_name, now.strftime('%m/%d/%y %H:%M:%S'), sandbox=sandbox
                    )[0]
                    break

                token = token_dict.get(api_name).get('token')
                break
        elif event == 'Get New Token':
            if get_confirmation(f'request a new token for the Dell {api_name} API'):
                window['-TOKEN-'].update(
                    request_access_token(
                        api_name,
                        datetime.now().strftime('%m/%d/%y %H:%M:%S'),
                        sandbox=sandbox
                    )[1]
                )
        else:
            continue

    window.close()
    return token


def get_credentials_layout(sandbox=False):
    '''
    Gets the PySimpleGUI layout for the Dell TechDirect username
    and password labels/input boxes.
    '''
    if sandbox is True:
        sandbox_elem = [sg.Checkbox(
            'Sandbox',
            key='-SANDBOX-',
            default=False,
            tooltip="Specifies whether or not to use the sandbox API endpoint"
        )]
    else:
        sandbox_elem = []

    return [
        [
            sg.T(
                'Username',
                size=TEXT_LABEL_SIZE,
                tooltip='The TechDirect username of the tech sumbitting the request'
            ),
            sg.I(key='-UN-', size=TEXT_INPUT_SIZE),
            *sandbox_elem,
        ],
        [
            sg.T(
                'Password',
                size=TEXT_LABEL_SIZE,
                tooltip='The TechDirect password of the tech sumbitting the request'
            ),
            sg.I(key='-PW-', password_char='*', size=TEXT_INPUT_SIZE),
        ]
    ]


def get_add_parts_notes_layout():
    '''
    Gets the PySimpleGUI layout for the Add/View/Replace/Remove
    parts and notes buttons.
    '''
    return [
        [
            sg.Frame('Part Numbers', layout=[
                [
                    sg.Button(
                        'Add Part',
                        tooltip='Adds a part number and (optionally) its PPID to the request',
                        key='-ADD_PART-',
                        button_color=SUBMIT_COLOR,
                        enable_events=True
                    ),
                    sg.Button(
                        'View Parts',
                        tooltip='Views all part numbers and PPIDs in the request',
                        key='-VIEW_PARTS-',
                        button_color=VIEW_COLOR,
                        enable_events=True
                    ),
                    sg.Button(
                        'Replace Parts',
                        tooltip='Replaces all part numbers and PPIDs with a new part number '
                        'and (optionally) PPID',
                        key='-REPLACE_PARTS-',
                        button_color=('black', '#ffa500'),
                        enable_events=True
                    ),
                    sg.Button(
                        'Remove Part',
                        tooltip='Removes a part number and its PPID from the request',
                        key='-REMOVE_PART-',
                        button_color=CANCEL_COLOR,
                        enable_events=True
                    )
                ],
            ], pad=(5, 5) )
        ],
        [
            sg.Frame('Note', layout=[
                [
                    sg.Button(
                        'Add Note',
                        tooltip='Adds a note to the request',
                        key='-ADD_NOTE-',
                        button_color=SUBMIT_COLOR,
                        enable_events=True
                    ),
                    sg.Button(
                        'View Note',
                        tooltip='Views the request note',
                        button_color=VIEW_COLOR,
                        key='-VIEW_NOTE-',
                        enable_events=True
                    ),
                    sg.Button(
                        'Replace Note',
                        tooltip='Replaces the existing note with a new note',
                        key='-REPLACE_NOTE-',
                        button_color=('black', '#ffa500'),
                        enable_events=True
                    ),
                    sg.Button(
                        'Remove Note',
                        button_color=CANCEL_COLOR,
                        tooltip='Removes the note from the request',
                        key='-REMOVE_NOTE-',
                        enable_events=True
                    )
                ],
            ], pad=(5, 5) )
        ]
    ]
